﻿namespace LiteBlog.Components
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using LiteBlog.Common.Entities;
    using LiteBlog.Components.Enums;
    using LiteBlog.Components.Contracts;
    using LiteBlog.Common.Components;
    using LiteBlog.Repositories.Contracts;
    using LiteBlog.Components.Helpers;
    using System.Web;
    using System.Globalization;

    public class BlogComp : BaseComp<IBlogRepository>, IBlogComp
    {
        public BlogComp(IBlogRepository data,
            IPostRepository postRepo,
            IArchiveRepository archiveRepo,
            ICategoryRepository categoryRepo,
            ITagRepository tagRepo,
            ICommentRepository commentRepo,
            ICacheComp cache,
            IDocumentComp documentComp,
            ISiteMapComp sitemapComp
            ) : base(data, cache)
        {
            _postRepo = postRepo;
            _archiveRepo = archiveRepo;
            _categoryRepo = categoryRepo;
            _tagRepo = tagRepo;
            _commentRepo = commentRepo;
            _documentComp = documentComp;
            _sitemapComp = sitemapComp;
        }

        public IDocumentComp DocumentComponent
        {
            get
            {
                return _documentComp;
            }
        }

        public ISiteMapComp SiteMapComponent
        {
            get
            {
                return _sitemapComp;
            }
        }


        public IEnumerable<PostInfo> Get()
        {
            return _data.Get();
        }

        public List<PostInfo> GetPublished()
        {
            return GetBlogInfo().Posts.OrderByDescending(p => p.UpdatedTime).ToList();
        }

        public PostInfo Get(string id)
        {
            return Get().SingleOrDefault(p => p.FileID == id);
        }

        public MethodResult Create(PostInfo postInfo)
        {
            var post = _postRepo.Create(postInfo.Title);
            postInfo.FileID = post.DraftID;
            return MethodResult.CreateSuccess;
        }

        public MethodResult Update(string olid, PostInfo pi)
        {
            var postInfo = Get(olid);
            var oldCategories = postInfo.Categories;
            postInfo.Title = pi.Title;
            postInfo.Categories = pi.Categories;
            _postRepo.Update(postInfo);
            if (!DraftPost.IsDraft(pi.FileID))
            {
                UpdateCategories(oldCategories, postInfo.Categories);
            }
            return MethodResult.UpdateSuccess;
        }

        public MethodResult Delete(string id)
        {
            if (!DraftPost.IsDraft(id))
            {
                DeletePostMeta(id);
            }

            _postRepo.Delete(id);
            SiteMapComponent.Delete(SiteMapType.Post, GetSiteMapUrl(id).Location);
            return MethodResult.DeleteSuccess;
        }

        public List<PostInfo> GetPostsByAuthor(string author)
        {
            PostCollection postCol = GetBlogInfo();
            List<PostInfo> posts = postCol.Posts;
            return posts.Where(pi => pi.Author == author).ToList();
        }


        public List<PostInfo> GetPostsByCategory(string catID)
        {
            List<PostInfo> list = new List<PostInfo>();
            PostCollection postCol = GetBlogInfo();
            if (postCol.CategoryMap.ContainsKey(catID))
            {
                list = postCol.CategoryMap[catID];
            }
            else
            {
                var posts = postCol.Posts;
                list = posts.Where(p => p.Categories.Select(c => c.CatID).Contains(catID)).ToList();
                postCol.CategoryMap[catID] = list;
                _cache.Put(CacheType.Blog, postCol);
            }

            return list;
        }

        public List<PostInfo> GetPostsByTag(string tagId)
        {
            List<PostInfo> list = new List<PostInfo>();
            PostCollection postCol = GetBlogInfo();
            if (postCol.TagMap.ContainsKey(tagId))
            {
                list = postCol.TagMap[tagId];
            }
            else
            {
                var posts = postCol.Posts;
                list = posts.Where(p => p.Tags.Select(t => t.TagId).Contains(tagId)).ToList();
                postCol.TagMap[tagId] = list;
                _cache.Put(CacheType.Blog, postCol);
            }
            return list;
        }

        public List<PostInfo> GetPostsByMonth(int year, int month)
        {
            string monthId = ArchiveMonth.GetMonthId(new DateTime(year, month, 1));

            List<PostInfo> list = new List<PostInfo>();
            PostCollection postCol = GetBlogInfo();
            if (postCol.CategoryMap.ContainsKey(monthId))
            {
                list = postCol.CategoryMap[monthId];
            }
            else
            {
                List<PostInfo> posts = postCol.Posts;
                var qry = from postInfo in posts where postInfo.MonthId == monthId select postInfo;
                list = qry.ToList<PostInfo>();
                postCol.CategoryMap[monthId] = list;
                _cache.Put(CacheType.Blog, postCol);
            }

            return list;
        }

        public bool PostExists(string fileId, out string fileId2)
        {
            fileId2 = string.Empty;
            var post = Get().SingleOrDefault(p => p.FileID.ToLower() == fileId.ToLower());
            if (post != null)
                fileId2 = post.FileID;
            return post != null ? true : false;
        }

        private PostCollection GetBlogInfo()
        {
            PostCollection postCol = _cache.Get<PostCollection>(CacheType.Blog);
            if (postCol == null)
            {
                List<PostInfo> posts = _data.GetPublished();
                postCol = new PostCollection();
                postCol.Posts = posts;
                _cache.Put(CacheType.Blog, postCol);
            }
            return postCol;
        }

        public MethodResult Publish(DraftPost post)
        {
            if (post.Time > DateTimeHelper.Now)
            {
                _data.Schedule(post.DraftID, post.Time);
                return MethodResult.PublishSuccess; // scheduled
            }

            post.FileID = _postRepo.Publish(post);

            if (post.Type == DraftType.Published)
            {
                UpdateCategories(post.OldCategories, post.Categories);
            }
            else
            {
                UpdateCategories(post.Categories);
            }

            if (post.Type == DraftType.Published)
                UpdateTags(post.OldTags, post.Tags);
            else
                UpdateTags(post.Tags);

            if (post.Type != DraftType.Published)
            {
                IncrementArchive(post.Time.Month, post.Time.Year);
            }

            if (post.Type == DraftType.Published)
            {
                var originalPost = LoadPost(post.FileID);
                DocumentHelper.DeleteAttachments(DocumentComponent, post, originalPost);
            }

            if (post.Type == DraftType.New)
            {
                SiteMapComponent.Add(SiteMapType.Post, GetSiteMapUrl(post.FileID));
            }
            else
            {
                SiteMapComponent.Update(SiteMapType.Post, GetSiteMapUrl(post.FileID));
            }

            if (post.Type == DraftType.New)
            {
                AddPostToStat(post.AsPostInfo());
            }

            return MethodResult.PublishSuccess;
        }

        public MethodResult PublishScheduled()
        {
            List<PostInfo> drafts = _data.GetScheduled();
            foreach (PostInfo postInfo in drafts)
            {
                if (postInfo.Time < DateTimeHelper.Now)
                {
                    DraftPost post = (DraftPost)LoadPost(postInfo.FileID);
                    Publish(post);
                }
            }
            return MethodResult.PublishSuccess;
        }

        public MethodResult Unpublish(string fileId, out string draftId)
        {
            DeletePostMeta(fileId);
            draftId = _postRepo.GetDraftIdIfDraftExistsForPublishedPost(fileId);
            if (string.IsNullOrEmpty(draftId))
            {
                draftId = _postRepo.Unpublish(fileId);
            }
            SiteMapComponent.Delete(SiteMapType.Post, GetSiteMapUrl(fileId).Location);
            return MethodResult.UnpublishSuccess;
        }

        private void UpdateCategories(List<Category> oldCategories, List<Category> newCategories)
        {
            if (!oldCategories.SequenceEqual(newCategories, Category.Comparer))
            {
                foreach (var oldCategory in oldCategories)
                {
                    if (!newCategories.Contains(oldCategory, Category.Comparer))
                    {
                        DecrementCategory(oldCategory.CatID);
                    }
                }

                foreach (var newCategory in newCategories)
                {
                    if (!oldCategories.Contains(newCategory, Category.Comparer))
                    {
                        IncrementCategory(newCategory.CatID);
                    }
                }
            }
        }

        private void UpdateCategories(List<Category> categories)
        {
            foreach (var category in categories)
            {
                IncrementCategory(category.CatID);
            }
        }

        private void UpdateTags(IEnumerable<Tag> oldTags, IEnumerable<Tag> newTags)
        {
            if (!oldTags.SequenceEqual(newTags, Tag.Comparer))
            {
                foreach (var oldTag in oldTags)
                {
                    if (!newTags.Contains(oldTag, Tag.Comparer))
                    {
                        DecrementTag(oldTag.TagId);
                    }
                }

                foreach (var newTag in newTags)
                {
                    if (!oldTags.Contains(newTag, Tag.Comparer))
                    {
                        IncrementTag(newTag.TagId);
                    }
                }
            }
        }

        private void UpdateTags(IEnumerable<Tag> tags)
        {
            foreach (var tag in tags)
            {
                IncrementTag(tag.TagId);
            }
        }

        private void DeletePostMeta(string fileId)
        {
            var postInfo = Get(fileId);
            if (postInfo.Categories != null && postInfo.Categories.Count > 0)
            {
                foreach (var category in postInfo.Categories)
                {
                    DecrementCategory(category.CatID);
                }
            }

            DecrementArchive(postInfo.Time.Month, postInfo.Time.Year);

            var post = LoadPost(fileId);
            foreach (var comment in post.Comments)
            {
                _commentRepo.Delete(comment.ID);
            }
        }

        public void DeleteAttachments(string fileId)
        {
            var post = LoadPost(fileId);
            var draftPost = post as DraftPost;
            bool removeCompletely = false;
            if (draftPost != null)
            {
                removeCompletely = (draftPost.Type == DraftType.New) || (draftPost.Type == DraftType.Unpublished);
            }
            else
            {
                removeCompletely = string.IsNullOrEmpty(_postRepo.GetDraftIdIfDraftExistsForPublishedPost(fileId));
            }

            // removeCompletely = remove all documents
            if (removeCompletely)
            {
                foreach (var document in post.Documents)
                {
                    DocumentComponent.Remove(document.Id);
                }
            }
            else
            {
                // removePartially
                if (draftPost != null)
                {
                    // we are deleting a draft post, while published post exists.
                    var publishedPost = LoadPost(draftPost.FileID);
                    DocumentHelper.DeleteAttachments(DocumentComponent, publishedPost, draftPost);
                }
                else
                {
                    // we are deleting a published post, while a draft post exists.
                    draftPost = LoadDraftForPublishedPost(fileId);
                    DocumentHelper.DeleteAttachments(DocumentComponent, draftPost, post);
                }
            }
        }

        private Post LoadPost(string fileId)
        {
            var post = _postRepo.Load(fileId);
            post.FormattedTime = post.Time.ToString(ConfigComp.DateFormat, CultureInfo.InvariantCulture);
            post.HighlightedContents = SyntaxHighlighter.Highlight(post.Contents);

            foreach (var c in post.Comments)
            {
                c.FormattedTime = c.Time.ToString(ConfigComp.DateFormat, CultureInfo.InvariantCulture);
            }

            return post;
        }

        private DraftPost LoadDraftForPublishedPost(string fileId)
        {
            DraftPost draftPost = null;
            string draftId = _postRepo.GetDraftIdIfDraftExistsForPublishedPost(fileId);
            if (!string.IsNullOrEmpty(draftId))
            {
                draftPost = (DraftPost)LoadPost(draftId);
            }
            return draftPost;
        }

        private SiteMapUrl GetSiteMapUrl(string fileId)
        {
            var siteMapUrl = new SiteMapUrl
            {
                Location = "/Post/" + fileId,
                LastModifiedTime = DateTime.UtcNow
            };
            return siteMapUrl;
        }

        private void IncrementCategory(string catId)
        {
            if (!string.IsNullOrEmpty(catId))
            {
                var category = _categoryRepo.Get(catId);
                category.Count++;
                _categoryRepo.Update(catId, category);
            }
        }

        private void DecrementCategory(string catId)
        {
            if (!string.IsNullOrEmpty(catId))
            {
                var category = _categoryRepo.Get(catId);
                category.Count--;
                _categoryRepo.Update(catId, category);
            }
        }

        private void IncrementArchive(int month, int year)
        {
            int archiveId = ArchiveMonth.GetArchiveId(month, year);
            var archive = _archiveRepo.Get(archiveId.ToString());
            if (archive == null)
            {
                archive = new ArchiveMonth(archiveId);
                archive.Count = 1;
                _archiveRepo.Create(archive);
            }
            else
            {
                archive.Count++;
                _archiveRepo.Update(archive.Id.ToString(), archive);
            }
        }

        private void DecrementArchive(int month, int year)
        {
            int archiveId = ArchiveMonth.GetArchiveId(month, year);
            var archive = _archiveRepo.Get(archiveId.ToString());
            if (archive != null)
            {
                if (archive.Count == 1)
                {
                    _archiveRepo.Delete(archiveId.ToString());
                }
                else
                {
                    archive.Count--;
                    _archiveRepo.Update(archiveId.ToString(), archive);
                }
            }
        }

        internal void IncrementTag(string tagId)
        {
            if (!string.IsNullOrEmpty(tagId))
            {
                var tag = _tagRepo.Get(tagId);
                tag.Count++;
                _tagRepo.Update(tagId, tag);
            }
        }

        internal void DecrementTag(string tagId)
        {
            if (!string.IsNullOrEmpty(tagId))
            {
                var tag = _tagRepo.Get(tagId);
                tag.Count--;
                _tagRepo.Update(tagId, tag);
            }
        }


        private void AddPostToStat(PostInfo postInfo)
        {
            Stat stat = (Stat)HttpContext.Current.Application["Stat"];
            if (!stat.PageVisits.ContainsKey(postInfo.FileID))
            {
                stat.PageVisits.Add(postInfo.FileID, postInfo);
            }
            HttpContext.Current.Application["Stat"] = stat;
        }

        private IPostRepository _postRepo;
        private IArchiveRepository _archiveRepo;
        private ICategoryRepository _categoryRepo;
        private ITagRepository _tagRepo;
        private ICommentRepository _commentRepo;
        private IDocumentComp _documentComp;
        private ISiteMapComp _sitemapComp;
    }
}